<!DOCTYPE html>
<html>
<head>
  <title>regl-gpu-lines Example</title>
  <meta charset='utf-8'>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/styles/default.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/highlight.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.3.1/languages/javascript.min.js"></script>
</head>
<body>
<script src="https://unpkg.com/controls-state@2.0.0/dist/controls-state.min.js"></script>
<script src="https://unpkg.com/controls-gui@2.0.0/dist/controls-gui.min.js"></script>
<script src="https://unpkg.com/regl@2.1.0/dist/regl.js"></script>
<script src="https://unpkg.com/regl-gpu-lines@latest"></script>
<script src="https://unpkg.com/gl-matrix@3.4.3/gl-matrix-min.js"></script>

<script>
const regl = createREGL({extensions: [
  'ANGLE_instanced_arrays',
  'OES_standard_derivatives',
]});
const createCamera = require('regl-camera');

const state = window.fuckit = wrapGUI(State({
  count: State.Slider(8, {min: 1, max: 50, step: 1}),
}));

const createREGLProxy = function (regl, argWrapper) {
  const proxy = args => regl({...args, ...argWrapper(args)});
  Object.assign(proxy, regl);
  return proxy;
}

const reglProxy = createREGLProxy(regl, function (args) {
  if (!args.vert) return {};
  return {
    vert: args.vert.slice(0, args.vert.length - 1) + `
      gl_Position = projection * view * gl_Position.zyxw;
    }`
  }
});

const drawLines = reglLines(reglProxy, {
  debug: true,
  vert: `
    precision highp float;
    uniform float pixelRatio, width;
    uniform vec2 aspect;
    uniform mat4 projection, view;

    #pragma lines: attribute vec3 xyz;
    #pragma lines: attribute float t;
    #pragma lines: varying float t = getT(t);
    #pragma lines: position = getPosition(xyz);
    #pragma lines: width = getWidth();

    float getT(float t) { return t; }
    vec4 getPosition(vec3 xyz) { return vec4(xyz.xy * aspect, xyz.z, 1); }
    float getWidth() { return width * pixelRatio; }
    `,
  frag: `
    #extension GL_OES_standard_derivatives : enable
    precision highp float;
    uniform float pixelRatio;
    varying float instanceID, t;
    varying vec2 triStripCoord;

    // Unit grid lines
    float grid (vec3 parameter, float width, float feather) {
      float w1 = width - feather * 0.5;
      vec3 d = fwidth(parameter);
      vec3 looped = 0.5 - abs(mod(parameter, 1.0) - 0.5);
      vec3 a3 = smoothstep(d * w1, d * (w1 + feather), looped);
      return min(min(a3.x, a3.y), a3.z);
    }

    void main () {
      if (instanceID < 0.0) {
        // End caps are red
        gl_FragColor.rgb = vec3(0.8, 0.1, 0.4);
      } else {
        // Remaining segments alternate blues
        gl_FragColor.rgb = mod(instanceID, 2.0) == 0.0 ? vec3(0.4, 0.7, 1.0) : vec3(0.2, 0.3, 0.7);
      }

      gl_FragColor.rgb *= fract(t * 32.0) > 0.5 ? 1.0 : 0.7;

      // Draw unit grid lines and a diagonal line using the vertex ID turned into a vec2 varying.
      //
      //   0     2     4     6     8
      //   + --- + --- + --- + --- +
      //   |   / |   / |   / |   / |
      //   | /   | /   | /   | /   |
      //   + --- + --- + --- + --- +
      //   1     3     5     7     9
      //
      float wire = grid(vec3(triStripCoord, triStripCoord.x + triStripCoord.y), 0.5 * pixelRatio, 1.0);
      gl_FragColor.rgb = mix(vec3(1), gl_FragColor.rgb, wire);

      //gl_FragColor.rgb *= 0.8;
      gl_FragColor.a = 1.0;
    }`,
  uniforms: {
    pixelRatio: regl.context('pixelRatio'),
    aspect: ctx => [1, ctx.framebufferWidth / ctx.framebufferHeight],
    width: regl.prop('width')
  },
  blend: {
    enable: false,
    func: {
      srcRGB: 'src alpha',
      srcAlpha: 1,
      dstRGB: 'one minus src alpha',
      dstAlpha: 1
    }
  },
  depth: {
    enable: true
  }
});

// Construct an array of xy pairs
const path = [
  [-1, 0.001],
  [-1, 0.0],
  //[-0.5, -0.5],
  //[-0.25, 0.5],
  //[0.0, 0.0],
  //[0.01, 0.0],
  //[0.02, 0.0],
  //[0.03, 0.0],
  //[0.04, 0.0],
  //[0.05, 0.0],
  [0.0, 0.0],
  [0.0, 0.5],
  [0.0, 0.],
  //[0.07, 0.0],
  //[0.25, 0.0],
  //[0.5, -0.5],
  [0.75, 0.],
  [1, 0.0]
];
const n = 9;//path.length;
const t = [...Array(n).keys()]
  .map(i => (i / (n - 1) * 2.0 - 1.0))
const xyz = t.map((t, i) =>
  //path[i].concat([t])
  [0.8 * Math.cos(2 * t * 3.14 - 0.5), 0.8 * Math.sin(t * 3.14), t]
);

// Set up the data to be drawn. Note that we preallocate buffers and don't create
// them on every draw call.
const lineData = {
  width: 100,
  join: 'round',
  cap: 'round',
  joinResolution: 2,
  vertexCount: xyz.length,
  vertexAttributes: {
    xyz: regl.buffer(xyz),
    t: regl.buffer(t),
  },
  endpointCount: 2,
  endpointAttributes: {
    xyz: regl.buffer([xyz.slice(0, 3), xyz.slice(-3).reverse()]),
    t: regl.buffer([t.slice(0, 3), t.slice(-3).reverse()]),
  }
};

const camera = createCamera(regl, {
  noScroll: true,
  theta: 2 * Math.PI / 2 - 0.5,
  phi: 0.5,
  distance: 40,
  near: 0.1,
  far: 100.0,
  fovy: 0.07,
  damping: 0,
});

let dirty = true;
regl.frame(({tick}) => {
  camera(/*{dtheta: Math.PI / 100 * Math.sin(tick / 100)},*/ state => {
    if (!state.dirty && !dirty) return;
    regl.clear({color: [0.2, 0.2, 0.2, 1]});
    drawLines(lineData);
    dirty = false;
  });
});
state.$onChange(() => dirty = true);

</script>
<style>
#code-container {
  font-family: sans-serif;
  position: absolute;
  left: 0;
  z-index: 10;
  max-height: 90%;
  overflow: auto;
  background-color: white;
}
#code-container summary {
  padding: 15px;
  cursor: pointer;
}
</style>
<div id="code-container">
<details>
<summary>Code</summary>
<pre><code id="code">
const regl = createREGL({extensions: [
  'ANGLE_instanced_arrays',
  'OES_standard_derivatives',
]});
const createCamera = require('regl-camera');

const state = window.fuckit = wrapGUI(State({
  count: State.Slider(8, {min: 1, max: 50, step: 1}),
}));

const createREGLProxy = function (regl, argWrapper) {
  const proxy = args => regl({...args, ...argWrapper(args)});
  Object.assign(proxy, regl);
  return proxy;
}

const reglProxy = createREGLProxy(regl, function (args) {
  if (!args.vert) return {};
  return {
    vert: args.vert.slice(0, args.vert.length - 1) + `
      gl_Position = projection * view * gl_Position.zyxw;
    }`
  }
});

const drawLines = reglLines(reglProxy, {
  debug: true,
  vert: `
    precision highp float;
    uniform float pixelRatio, width;
    uniform vec2 aspect;
    uniform mat4 projection, view;

    #pragma lines: attribute vec3 xyz;
    #pragma lines: attribute float t;
    #pragma lines: varying float t = getT(t);
    #pragma lines: position = getPosition(xyz);
    #pragma lines: width = getWidth();

    float getT(float t) { return t; }
    vec4 getPosition(vec3 xyz) { return vec4(xyz.xy * aspect, xyz.z, 1); }
    float getWidth() { return width * pixelRatio; }
    `,
  frag: `
    #extension GL_OES_standard_derivatives : enable
    precision highp float;
    uniform float pixelRatio;
    varying float instanceID, t;
    varying vec2 triStripCoord;

    // Unit grid lines
    float grid (vec3 parameter, float width, float feather) {
      float w1 = width - feather * 0.5;
      vec3 d = fwidth(parameter);
      vec3 looped = 0.5 - abs(mod(parameter, 1.0) - 0.5);
      vec3 a3 = smoothstep(d * w1, d * (w1 + feather), looped);
      return min(min(a3.x, a3.y), a3.z);
    }

    void main () {
      if (instanceID < 0.0) {
        // End caps are red
        gl_FragColor.rgb = vec3(0.8, 0.1, 0.4);
      } else {
        // Remaining segments alternate blues
        gl_FragColor.rgb = mod(instanceID, 2.0) == 0.0 ? vec3(0.4, 0.7, 1.0) : vec3(0.2, 0.3, 0.7);
      }

      gl_FragColor.rgb *= fract(t * 32.0) > 0.5 ? 1.0 : 0.7;

      // Draw unit grid lines and a diagonal line using the vertex ID turned into a vec2 varying.
      //
      //   0     2     4     6     8
      //   + --- + --- + --- + --- +
      //   |   / |   / |   / |   / |
      //   | /   | /   | /   | /   |
      //   + --- + --- + --- + --- +
      //   1     3     5     7     9
      //
      float wire = grid(vec3(triStripCoord, triStripCoord.x + triStripCoord.y), 0.5 * pixelRatio, 1.0);
      gl_FragColor.rgb = mix(vec3(1), gl_FragColor.rgb, wire);

      //gl_FragColor.rgb *= 0.8;
      gl_FragColor.a = 1.0;
    }`,
  uniforms: {
    pixelRatio: regl.context('pixelRatio'),
    aspect: ctx => [1, ctx.framebufferWidth / ctx.framebufferHeight],
    width: regl.prop('width')
  },
  blend: {
    enable: false,
    func: {
      srcRGB: 'src alpha',
      srcAlpha: 1,
      dstRGB: 'one minus src alpha',
      dstAlpha: 1
    }
  },
  depth: {
    enable: true
  }
});

// Construct an array of xy pairs
const path = [
  [-1, 0.001],
  [-1, 0.0],
  //[-0.5, -0.5],
  //[-0.25, 0.5],
  //[0.0, 0.0],
  //[0.01, 0.0],
  //[0.02, 0.0],
  //[0.03, 0.0],
  //[0.04, 0.0],
  //[0.05, 0.0],
  [0.0, 0.0],
  [0.0, 0.5],
  [0.0, 0.],
  //[0.07, 0.0],
  //[0.25, 0.0],
  //[0.5, -0.5],
  [0.75, 0.],
  [1, 0.0]
];
const n = 9;//path.length;
const t = [...Array(n).keys()]
  .map(i => (i / (n - 1) * 2.0 - 1.0))
const xyz = t.map((t, i) =>
  //path[i].concat([t])
  [0.8 * Math.cos(2 * t * 3.14 - 0.5), 0.8 * Math.sin(t * 3.14), t]
);

// Set up the data to be drawn. Note that we preallocate buffers and don't create
// them on every draw call.
const lineData = {
  width: 100,
  join: 'round',
  cap: 'round',
  joinResolution: 2,
  vertexCount: xyz.length,
  vertexAttributes: {
    xyz: regl.buffer(xyz),
    t: regl.buffer(t),
  },
  endpointCount: 2,
  endpointAttributes: {
    xyz: regl.buffer([xyz.slice(0, 3), xyz.slice(-3).reverse()]),
    t: regl.buffer([t.slice(0, 3), t.slice(-3).reverse()]),
  }
};

const camera = createCamera(regl, {
  noScroll: true,
  theta: 2 * Math.PI / 2 - 0.5,
  phi: 0.5,
  distance: 40,
  near: 0.1,
  far: 100.0,
  fovy: 0.07,
  damping: 0,
});

let dirty = true;
regl.frame(({tick}) => {
  camera(/*{dtheta: Math.PI / 100 * Math.sin(tick / 100)},*/ state => {
    if (!state.dirty && !dirty) return;
    regl.clear({color: [0.2, 0.2, 0.2, 1]});
    drawLines(lineData);
    dirty = false;
  });
});
state.$onChange(() => dirty = true);

</code></pre>
</details>
</div>
<script>
const code = document.getElementById('code');
hljs.highlightElement(code);
</script>
</body>
</html>
